package com.huawei.deveco.common.security.encrypt;

import com.huawei.deveco.common.security.exception.CryptoException;
import com.huawei.deveco.common.security.util.DRBG;

import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

final class AesGcmKit implements CryptoKit {
    private static final int GCM_IV_LENGTH = 12;

    private static final int GCM_TAG_LENGTH = 16;

    private static final int EIGHT_BIT = 8;

    private static final int CONTENT_LENGTH_TAG = 4;

    private static final int MIN_ENCRYPTED_LENGTH = 32;

    public byte[] encrypt(byte[] key, byte[] msgBytes) throws CryptoException {
        byte[] ivKey = new byte[12];
        DRBG.getSecureRandom().nextBytes(ivKey);
        try {
            Cipher cipher = createGcmCipher(1, createKeySpec(key), createGcmParameterSpec(ivKey));
            byte[] content = cipher.doFinal(msgBytes);
            int length = content.length;
            byte[] data = new byte[4 + ivKey.length + length];
            data[0] = (byte) (length >> 24 & 0xFF);
            data[1] = (byte) (length >> 16 & 0xFF);
            data[2] = (byte) (length >> 8 & 0xFF);
            data[3] = (byte) (length & 0xFF);
            System.arraycopy(ivKey, 0, data, 4, ivKey.length);
            System.arraycopy(content, 0, data, 4 + ivKey.length, length);
            return data;
        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException |
                 InvalidKeyException | javax.crypto.BadPaddingException | javax.crypto.IllegalBlockSizeException e) {
            throw new CryptoException("Encrypt failed", e);
        }
    }

    public byte[] decrypt(byte[] key, byte[] msgBytes) throws CryptoException {
        if (msgBytes.length < 32)
            throw new CryptoException("Illegal data");
        try {
            int bodyLength = (msgBytes[0] & 0xFF) << 24 | (msgBytes[1] & 0xFF) << 16 | (msgBytes[2] & 0xFF) << 8 | msgBytes[3] & 0xFF;
            int ivLength = msgBytes.length - 4 - bodyLength;
            byte[] ivKey = (new IvParameterSpec(msgBytes, 4, ivLength)).getIV();
            Cipher cipher = createGcmCipher(2, createKeySpec(key),
                    createGcmParameterSpec(ivKey));
            return cipher.doFinal(msgBytes, 4 + ivLength, bodyLength);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException |
                 InvalidKeyException | javax.crypto.BadPaddingException | javax.crypto.IllegalBlockSizeException e) {
            throw new CryptoException("Decrypt failed", e);
        }
    }

    private Cipher createGcmCipher(int mode, SecretKeySpec secretKeySpec, GCMParameterSpec gcmParameterSpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(mode, secretKeySpec, gcmParameterSpec);
        return cipher;
    }

    private GCMParameterSpec createGcmParameterSpec(byte[] ivKey) {
        return new GCMParameterSpec(128, ivKey);
    }

    private SecretKeySpec createKeySpec(byte[] key) {
        return new SecretKeySpec(key, "AES");
    }
}
